#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/time.h>

#include "fdtd_aceMesh.h"
#include "aceMesh_runtime_c.h"
/*
#define TMAX 30
#define NX 2048
#define NY 2048
*/
#define FREQ 1500000000

#define min(a,b) ((a>b)?(b):(a))
#define max(a,b) ((a>b)?(a):(b))
double ex[NX][NY+1];
double ey[NX+1][NY];
double hz[NX][NY];
double ex1[NX][NY+1]; 
double ey1[NX+1][NY]; 
double hz1[NX][NY];
#ifdef PROFILING 
unsigned long reuse_c=0;
unsigned long slave_reuse_count[64]={0};
unsigned long slave_comp_count[64]={0};
unsigned long slave_dma_count[64]={0};
unsigned long slave_ldm_dma_count[64]={0};
#endif
unsigned long rpcc()
{
  unsigned long time;
  asm("rtc %0":"=r"(time):);
  return time;
}
//int TZ1,TZ2;


extern void slave_stencil_loop1(targs1 *args1);
extern void slave_stencil_loop2(targs2 *each_loop2);
extern void slave_stencil_loop3(targs2 *each_loop3);
extern void slave_stencil_loop4(targs2 *each_loop4);

void neighbor_loop2(void** neighbor_addrs,int* num_neigh,targs2 *args2)
{
 int ii=args2->ii;
 void *addr=args2->addrs;
 *num_neigh = 0;
 if(ii>=TZ1)
    neighbor_addrs[(*num_neigh)++] = (void*)(addr-TZ1*NY*8);
 }

void neighbor_loop3(void** neighbor_addrs,int* num_neigh, targs2 *args3)
{
 int ii=args3->ii;
 int jj=args3->jj;
 void *addr=args3->addrs;
 *num_neigh = 0; 
 if(jj>=TZ2)
     neighbor_addrs[(*num_neigh)++] = (void*)(addr-TZ2*8);
}
void neighbor_loop4(void** neighbor_addrs,int* num_neigh, targs4 *args4)
{
  int ii=args4->ii;
  int jj=args4->jj;
  void *addr_ex=args4->addr_ex;//previous addr1
  void *addr_ey=args4->addr_ey;//previous addr2
  *num_neigh = 0;
  if(ii<NX-TZ1)
     neighbor_addrs[(*num_neigh)++] = (void*)(addr_ey+TZ1*NY*8);
  if(ii>=TZ2)
     neighbor_addrs[(*num_neigh)++] = (void*)(addr_ex-TZ2*8);
}

void init_array(double ex[][NY+1],double ey[][NY], double hz[][NY])
{
   int i, j;
   for(i = 0; i < NX+1; i++)
     for(j = 0; j < NY; j++)
        ey[i][j] = (double)((i+1)/(j+1));

   for(i = 0; i < NX; i++)
     for(j = 0; j < NY+1; j++)
        ex[i][j] =(double)(1/(i+j+1)); 


   for(i = 0; i < NX; i++)
     for(j = 0; j < NY; j++)
        hz[i][j] = (double)(1/(i+1)/(j+1));
   
}

int check()
{
    int i1,j1,t1;
    //YOU NEED INITIALIZATION HERE
    init_array(ex1,ey1,hz1);  //by lchen

    //........not parallel region and add hz1 for check.............	
    
    for(t1=0; t1<TMAX; t1++)  
    {
    //loop1
        for (j1=0; j1<NY; j1++)
            ey1[0][j1] = t1;
    //loop2
        for (i1=1; i1<NX; i1++)
            for (j1=0; j1<NY; j1++)
            {
    //   if(i1==1&&j1==0)
      //    printf("master ey1 iter %d,%20.20f,%20.20f,%20.20f\n",t1,ey1[i1][j1],hz1[i1][j1],hz1[i1-1][j1]);
                ey1[i1][j1] = ey1[i1][j1] - 0.5*(hz1[i1][j1]-hz1[i1-1][j1]);
            }
    //loop3
        for (i1=0; i1<NX; i1++)
            for (j1=1; j1<NY; j1++)
            {
       //if(i1==0&&j1==1)
         //printf("master ex1 iter %d,%20.20f,%20.20f,%20.20f\n",t1,ex1[i1][j1],hz1[i1][j1],hz1[i1][j1-1]);
                ex1[i1][j1] = ex1[i1][j1] - 0.5*(hz1[i1][j1]-hz1[i1][j1-1]);
            }
    //loop4
        for(i1=0; i1<NX; i1++)
            for(j1=0; j1<NY; j1++)
            {
                //if(i1==0&&j1==0)printf("iter:%d,master-loop4:%20.20f,%20.20f,%20.20f,%20.20f,%20.20f\n",t1,hz1[i1][j1],ex1[i1][j1+1],ex1[i1][j1],ey1[i1+1][j1],ey1[i1][j1]);
                hz1[i1][j1]=hz1[i1][j1]-0.7*(ex1[i1][j1+1]-ex1[i1][j1]+ey1[i1+1][j1]-ey1[i1][j1]);
            }
   //  printf("master hz1 :=====%20.20f,%20.20f\n",hz1[1][0],hz1[0][0]);
    }
//.............add it for check............
  //loop1-ey
  for(j1=0; j1<NY; j1++) 
  {
     if(fabs(ey1[0][j1]-ey[0][j1])>1e-15)
     {
        printf("loop1-ey<%d,%d >: ",i1,j1);
        printf("master=%f is incoistent with ",ey1[0][j1]);
        printf("slave=%f\n",ey[0][j1]);
        return 1;
     }
  }
  
  //loop2-ey 
  for(i1=1; i1<NX; i1++)
    for(j1=0; j1<NY; j1++)
    {
      if(fabs(ey1[i1][j1]-ey[i1][j1])>1e-15)
      {   
        printf("loop2-ey<%d,%d >: ",i1,j1);
        printf("master=%.20lf is incoistent with ",ey1[i1][j1]);
        printf("slave=%.20lf\n",ey[i1][j1]);
        return 1;
      } 
    }
  //loop3-ex
  for(i1=0; i1<NX; i1++)
    for(j1=1; j1<NY; j1++)  
    {
      if(fabs(ex1[i1][j1]-ex[i1][j1])>1e-15)
      {   
        printf("loop3-ex<%d,%d >: ",i1,j1);
        printf("master=%f is incoistent with ",ex1[i1][j1]);
        printf("slave=%f\n",ex[i1][j1]);
        return 1;
      }
    }
 //loop4-hz 
 for(i1=0;i1<NX;i1++)
  for(j1=0;j1<NY;j1++)
  {
    if(fabs(hz1[i1][j1]-hz[i1][j1])>1e-15)
    {
       printf("loop4-hz<%d,%d >: ",i1,j1);
       printf("master=%20.20f is incoistent with ",hz1[i1][j1]);
       printf("slave=%20.20f\n",hz[i1][j1]);
       return 1;
    }
  }
  printf("......check is right !......\n");
  return 0;
}

Dofdtd()
{
  int t,ii,jj;
  //int block[2];
  targs1 args1;
  targs2 args2;
  targs2 args3;
  targs4 args4;
  t = 0;
  //block[0]=TZ1;
  //block[1]=TZ2;
  //ldmbuf_add(block,2);
  //ldmbuf_bulid_end();
  for(t = 0; t < TMAX; t++)
  {
    int tile_no[3][2];
    // tile_no[0][0]= 0;
    // tile_no[0][1]= 0;
    // tile_no[1][0]= 0;
    // tile_no[1][1]= floor(NY/TZ2)-1;
    // tile_no[2][0]= tile_no[2][1]=-1;
    // acemesh_register_invalidatetab(&ey[0][0], (int *)tile_no);
    //if()
    acemesh_begin_split_task("loop1");
    for(jj = 0; jj < NY; jj+=TZ2)
    {   
       args1.loop_id=t*4;
       args1.t=t;
       args1.jj=jj;
       args1.addrs=&ey[0][jj];
       acemesh_push_wlist(1, &ey[0][jj], NORMAL);
       acemesh_task_generator((TASK_FUNCPTR)slave_stencil_loop1,(void*)(&args1),sizeof(targs1));
       acemesh_task_set_type(STENCIL_TASK);
    }
    acemesh_end_split_task(); 
    //acemesh_spawn_and_wait(1);

    // tile_no[0][0]= 0;
    // tile_no[0][1]= floor(NX/TZ1)-1;
    // tile_no[1][0]= 0;
    // tile_no[1][1]= floor(NY/TZ2)-1;
    // tile_no[2][0]= tile_no[2][1]=-1;
    // acemesh_register_invalidatetab(&ey[0][0], (int *)tile_no);
    acemesh_begin_split_task("loop2");
    for (ii = 0; ii < NX; ii+=TZ1)
    for (jj = 0; jj < NY; jj+=TZ2)
    {
        args2.loop_id=t*4+1;
        args2.ii=ii;
        args2.jj=jj;
        args2.iter=t;
        args2.addrs=&hz[ii][jj];
        acemesh_push_wrlist(1,&ey[ii][jj],NORMAL);
        acemesh_push_rlist(1, &hz[ii][jj], NORMAL);
        acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_loop2,
                      (void*)(&args2), sizeof(targs2),
                      NULL, (NEIGHBOR_FUNCPTR)neighbor_loop2, &args2);
        acemesh_task_set_type(STENCIL_TASK);
    }
    acemesh_end_split_task();
    //acemesh_spawn_and_wait(1);
    // tile_no[0][0]= 0;
    // tile_no[0][1]= floor(NX/TZ1)-1;
    // tile_no[1][0]= 0;
    // tile_no[1][1]= floor(NY/TZ2)-1;
    // tile_no[2][0]= tile_no[2][1]=-1;
    // acemesh_register_invalidatetab(&ex[0][0], (int *)tile_no); 
    acemesh_begin_split_task("loop3");
    for(ii = 0; ii < NX; ii+=TZ1)
    for(jj = 0;jj < NY;jj+=TZ2){
        args3.loop_id=t*4+2;
        args3.ii=ii;
        args3.jj=jj;
        args3.iter=t;
        args3.addrs=&hz[ii][jj];
        acemesh_push_wrlist(1,&ex[ii][jj],NORMAL);
        acemesh_push_rlist(1, &hz[ii][jj], NORMAL);
        acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_loop3,
                   (void*)(&args3), sizeof(targs2),
                   NULL, (NEIGHBOR_FUNCPTR)neighbor_loop3, &args3);
        acemesh_task_set_type(STENCIL_TASK);
    }
    acemesh_end_split_task();
    //acemesh_spawn_and_wait(1);
    // tile_no[0][0]= 0;
    // tile_no[0][1]= floor(NX/TZ1)-1;
    // tile_no[1][0]= 0;
    // tile_no[1][1]= floor(NY/TZ2)-1;
    // tile_no[2][0]= tile_no[2][1]=-1;
    // acemesh_register_invalidatetab(&hz[0][0], (int *)tile_no); 
    acemesh_begin_split_task("loop4");
    for(ii = 0; ii < NX; ii+=TZ1)
    for(jj = 0;jj < NY;jj+=TZ2){
        args4.loop_id=t*4+3;
        args4.ii=ii;
        args4.jj=jj;
        args4.iter=t;
        args4.addr_ey=&ey[ii][jj];
        args4.addr_ex=&ex[ii][jj];
        acemesh_push_wrlist(1,&hz[ii][jj],NORMAL);
        acemesh_push_rlist(1, &ex[ii][jj],NORMAL);
        acemesh_push_rlist(1, &ey[ii][jj],NORMAL);
        acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_loop4,
                      (void*)(&args4), sizeof(targs4),
                      NULL, (NEIGHBOR_FUNCPTR)neighbor_loop4, &args4);
        acemesh_task_set_type(STENCIL_TASK);
    }
    acemesh_end_split_task();
    //acemesh_spawn_and_wait(1);
  } //time iteration
  acemesh_spawn_and_wait(1);
}

int main(int argc, char** argv)
{
  int  num_threads;
  //struct timeval start;
  //struct timeval end;
  //double totaltime;
  unsigned long start1,end1;
  unsigned long comp_count=0,dma_count=0i,ldm_dma_count=0;
  int i,j;
  int p=1;                 //default tile size
  num_threads=4;         //default thread num
  if(argc>1)	
     num_threads =atoi(argv[1]);
  if((NX%TZ1) != 0 || (NY%TZ2) != 0)
  {   
      printf("(NX%TZ1) and (NY%TZ2) must == 0 \n");
      assert(0);
  }
  init_array(ex,ey,hz);
  acemesh_runtime_init(num_threads);
  //while(p);
  //gettimeofday(&start,NULL);
  start1=rpcc();
  Dofdtd();
  end1=rpcc();
  //gettimeofday(&end,NULL);
  //totaltime=(end.tv_sec-start.tv_sec)+ (end.tv_usec-start.tv_usec)*1.0e-6;
  acemesh_runtime_shutdown(); 

#ifdef PROFILING 
  //for(i=0;i<64;i++)
  //{
     // reuse_c+=count[i];
     // printf("count[%d]=%d\t",i,count[i]);
  //}
    j=0;
#ifdef _BLACKROW
    if(num_threads>PE_NCOL)
        j=PE_NCOL;
#endif
    printf("j=%d\n",j);
    for(i=0;i<num_threads;i++)
    {
        reuse_c+=slave_reuse_count[i];
        comp_count+=slave_comp_count[i];
        dma_count+=slave_dma_count[i];
        ldm_dma_count+=slave_ldm_dma_count[i];
        //printf("DAG_compute_count[%ld]=%ld\t",i,slave_comp_count[i]);
        //printf("DAG_dma_count[%1d]=%ld\n",i,slave_dma_count[i]);
    }
    printf("dma:");
    for(i=0;i<num_threads+j;i++)
    {
      printf(", %6.3f",(float)slave_dma_count[i]/FREQ);
      if(i%16==15) printf("\n");
    }
    printf("\n");
    printf("comp:");
    for(i=0;i<num_threads+j;i++)
    {
      printf(", %6.3f",(float)slave_comp_count[i]/FREQ);
      if(i%16==15) printf("\n");
    }
    printf("\n");
    printf("aver_ldm_dma=%5.3f\n",(float)ldm_dma_count/FREQ/num_threads);
    printf("aver_dma=%5.3f\n",(float)dma_count/FREQ/num_threads);
    printf("aver_comp=%5.3f\n",(float)comp_count/FREQ/num_threads);
    printf("resue=%ld\n",reuse_c);
#endif
  
#ifdef CHECK
   if(check()) 
   {
	printf("error!\n");
	exit(1);
   }
#endif
	//printf("fdtd-2d,using %d threads,total time = %f seconds\n",num_threads,totaltime);
	printf("fdtd-2d,using %d threads\ntotal time = %5.3f seconds\n",num_threads,(float)(end1-start1)/FREQ);

   return 0;
}                       
